Aqui tienes todo el codigo de la aplicacion terminada:
import FarfallaApp from "@/components/farfalla-app"
export default function Page() {
return <FarfallaApp />
}import { Analytics } from '@vercel/analytics/next'
import type { Metadata } from 'next'
import './globals.css'
export const metadata: Metadata = {
title: 'Farfalla Beauty Studio - Fichas Cosmetológicas',
description: 'Sistema de gestión de fichas cosmetológicas para profesionales de la belleza',
generator: 'v0.app',
manifest: '/manifest.json',
icons: {
icon: '/app-icon.png',
shortcut: '/app-icon.png',
apple: '/app-icon.png',
},
appleWebApp: {
capable: true,
statusBarStyle: 'default',
title: 'Farfalla',
},
}
export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode
}>) {
return (
<html lang="es">
<body className="font-sans antialiased" style={{background: "#fdfaf4"}}>
{children}
{process.env.NODE_ENV === 'production' && <Analytics />}
</body>
</html>
)
}{
"name": "Farfalla Beauty Studio",
"short_name": "Farfalla",
"description": "Sistema de gestión de fichas cosmetológicas para profesionales de la belleza",
"start_url": "/",
"display": "standalone",
"background_color": "#fdfaf4",
"theme_color": "#b8860b",
"icons": [
{
"src": "/app-icon.png",
"sizes": "192x192",
"type": "image/png",
"purpose": "any"
},
{
"src": "/app-icon.png",
"sizes": "512x512",
"type": "image/png",
"purpose": "any"
},
{
"src": "/app-icon.png",
"sizes": "180x180",
"type": "image/png",
"purpose": "maskable"
}
]
}El componente principal es muy largo (1125 lineas). Incluye:
Puedes descargar todo el codigo haciendo clic en los tres puntos (menu) arriba a la derecha y seleccionando "Download ZIP", o conectar a GitHub desde Settings.
Entendido, te paso el codigo completo para que lo puedas deployar en Netlify.
Aqui tienes todo el codigo completo para Netlify. Crea estos archivos en tu proyecto:
{
"name": "farfalla-beauty-studio",
"version": "1.0.0",
"private": true,
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start"
},
"dependencies": {
"next": "^15.0.0",
"react": "^19.0.0",
"react-dom": "^19.0.0"
},
"devDependencies": {
"@types/node": "^20",
"@types/react": "^19",
"@types/react-dom": "^19",
"typescript": "^5"
}
}/** @type {import('next').NextConfig} */
const nextConfig = {
output: 'export',
images: {
unoptimized: true
}
}
export default nextConfig{
"compilerOptions": {
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"strict": true,
"noEmit": true,
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "bundler",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve",
"incremental": true,
"plugins": [{"name": "next"}],
"paths": {"@/*": ["./*"]}
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
"exclude": ["node_modules"]
}@import "tailwindcss";
:root {
--background: #fdfaf4;
--foreground: #3a2e24;
}
body {
background: var(--background);
color: var(--foreground);
font-family: Georgia, serif;
}import type { Metadata } from 'next'
import './globals.css'
export const metadata: Metadata = {
title: 'Farfalla Beauty Studio - Fichas Cosmetológicas',
description: 'Sistema de gestión de fichas cosmetológicas para profesionales de la belleza',
manifest: '/manifest.json',
icons: {
icon: '/app-icon.png',
shortcut: '/app-icon.png',
apple: '/app-icon.png',
},
appleWebApp: {
capable: true,
statusBarStyle: 'default',
title: 'Farfalla',
},
}
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<html lang="es">
<body style={{background: "#fdfaf4"}}>
{children}
</body>
</html>
)
}import FarfallaApp from "@/components/farfalla-app"
export default function Page() {
return <FarfallaApp />
}{
"name": "Farfalla Beauty Studio",
"short_name": "Farfalla",
"description": "Sistema de gestión de fichas cosmetológicas",
"start_url": "/",
"display": "standalone",
"background_color": "#fdfaf4",
"theme_color": "#b8860b",
"icons": [
{
"src": "/app-icon.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "/app-icon.png",
"sizes": "512x512",
"type": "image/png"
}
]
}"use client"
import { useState, useEffect, useRef } from "react";
const BIOTIPOS = ["Grasa deshidratada", "Grasa", "Seca", "Sensible", "Mixta"];
const FOTOTIPOS = [
{ num:"1", desc:"Muy clara, siempre se quema, nunca se broncea" },
{ num:"2", desc:"Clara, generalmente se quema, bronceado mínimo" },
{ num:"3", desc:"Media, a veces se quema, bronceado moderado" },
{ num:"4", desc:"Oliva, rara vez se quema, bronceado bueno" },
{ num:"5", desc:"Morena, muy rara vez se quema" },
{ num:"6", desc:"Oscura, nunca se quema" },
];
const LESIONES = [
"Acné","Comedones","Pápulas","Pústulas","Quistes de millium",
"Descamación","Hiperpigmentación","Cicatrices atróficas",
"Poros visibles","Poros poco visibles","Hiperqueratosis",
"Eritema en zona de la nariz","Telangiectasia en aletas de nariz",
"Líneas de expresión","Flacidez",
];
const ALERGIAS_LIST = ["Almendras","Lácteos","Aspirina","Gluten","Ninguno"];
const ANTECEDENTES = ["Cardiovasculares","Oncológicos","Endocrinos (diabetes / tiroides)","Ninguno"];
const TRATAMIENTOS = [
"Limpieza facial profunda","Hidratación facial","Peeling químico",
"Microdermabrasión","Radiofrecuencia facial","Mesoterapia",
"Tratamiento anti-acné","Tratamiento despigmentante","Lifting facial",
"Oxigenoterapia","LED terapia","Otro",
];
interface Patient {
id: number;
foto: string | null;
nombre: string;
apellido: string;
fechaNacimiento: string;
telefono: string;
ocupacion: string;
biotipo: string;
fototipo: string;
alergias: string[];
otrasAlergias: string;
habitos: {
tabaquismo: boolean;
alcohol: boolean;
actividadFisica: boolean;
suplementos: string;
afeccionesCutaneas: string;
};
antecedentes: string[];
otrosAntecedentes: string;
lesiones: string[];
otrasLesiones: string;
medicamentos: string;
motivoConsulta: string;
rutinaActual: string[];
sesiones: Sesion[];
createdAt: string;
}
interface Sesion {
id: number;
fecha: string;
tratamiento: string;
productosUsados: string;
observaciones: string;
proximaSesion: string;
fotosAntes: string[];
fotosDespues: string[];
}
const emptyPatient = (): Patient => ({
id: Date.now(), foto: null,
nombre:"", apellido:"", fechaNacimiento:"", telefono:"", ocupacion:"",
biotipo:"", fototipo:"",
alergias:[], otrasAlergias:"",
habitos:{ tabaquismo:false, alcohol:false, actividadFisica:false, suplementos:"", afeccionesCutaneas:"" },
antecedentes:[], otrosAntecedentes:"",
lesiones:[], otrasLesiones:"",
medicamentos:"", motivoConsulta:"",
rutinaActual:["","","","",""],
sesiones:[],
createdAt: new Date().toISOString(),
});
const emptySesion = (): Sesion => ({
id: Date.now(),
fecha: new Date().toISOString().split("T")[0],
tratamiento:"", productosUsados:"", observaciones:"", proximaSesion:"",
fotosAntes:[], fotosDespues:[],
});
function calcEdad(fecha: string) {
if (!fecha) return "";
const hoy = new Date(), nac = new Date(fecha);
let e = hoy.getFullYear() - nac.getFullYear();
const m = hoy.getMonth() - nac.getMonth();
if (m < 0 || (m === 0 && hoy.getDate() < nac.getDate())) e--;
return e;
}
function fileToBase64(file: File): Promise<string> {
return new Promise((res, rej) => {
const r = new FileReader();
r.onload = () => res(r.result as string);
r.onerror =
<AssistantMessageContentPart partEncoded="eyJjcmVhdGVkQXQiOjE3ODA1ODAxMDc2NzgsImZpbmlzaGVkQXQiOjE3ODA1ODAxMDc2NzgsImxhc3RQYXJ0U2VudEF0IjoxNzgwNTgwMTA3Njc4LCJpZCI6Ijg2ZmVlclFPRHZmcWhxTnAiLCJ0eXBlIjoidGFzay1zdG9wcGVkLXYxIiwicGFydHMiOlt7InR5cGUiOiJtYW51YWxseS1zdG9wcGVkLW9uLWNsaWVudCJ9XX0=" />